using Crux.Core.Dapper.SqlGenerator;

namespace Crux.Core.Dapper;

public partial class UnitOfWork<TContext> where TContext : DapperContext
{
    /// <inheritdoc/>
    public TEntity Add<TEntity>(
        TEntity entity,
        Action<SqlGenerator<TEntity>>? configure = null) where TEntity : class
    {
        var generator = GetSqlGenerator(configure);

        var sqlQuery = generator.GetInsert(entity);
        if (generator.IsIdentity)
        {
            if (generator.Provider == SqlProvider.Oracle)
            {
                this.Execute(sqlQuery.GetSql(), sqlQuery.Param);

                var newId = ((DynamicParameters)sqlQuery.Param!).Get<int>(":newId");
                var success = SetValue(generator, newId, entity);

                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"generate id is {newId},{success}");

                return entity;
            }
            else
            {
                var newId = Query(sqlQuery.GetSql(), sqlQuery.Param);
                bool success = SetValue(generator, newId, entity);

                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"generate id is {newId},{success}");
                return entity;
            }
        }

        this.Execute(sqlQuery.GetSql(), entity);
        return entity;
    }
    /// <inheritdoc/>
    public int AddRange<TEntity>(
        IEnumerable<TEntity> entities,
        Action<SqlGenerator<TEntity>>? configure = null) where TEntity : class
    {
        var generator = GetSqlGenerator(configure);
        if (generator.Provider == SqlProvider.MSSQL)
        {
            var count = 0;
            var totalInstances = entities.Count();

            var properties =
                (generator.IsIdentity
                    ? generator.SqlProperties.Where(p =>
                        !p.PropertyName.Equals(generator.IdentitySqlProperty!.PropertyName,
                            StringComparison.OrdinalIgnoreCase))
                    : generator.SqlProperties).ToList();

            var exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d);
            if (exceededTimes > 1)
            {
                var maxAllowedInstancesPerBatch = totalInstances / exceededTimes;
                if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000;

                var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch);

                for (var i = 0; i <= maxIterationCount; i++)
                {
                    var skips = i * maxAllowedInstancesPerBatch;

                    if (skips >= totalInstances)
                        break;

                    var items = entities.Skip(skips).Take(maxAllowedInstancesPerBatch);
                    var msSqlQueryResult = generator.GetBulkInsert(items);

                    count += Execute(msSqlQueryResult.GetSql(), msSqlQueryResult.Param);
                }

                return count;
            }
        }

        var queryResult = generator.GetBulkInsert(entities);

        return Execute(queryResult.GetSql(), queryResult.Param);
    }
    /// <inheritdoc/>
    public async Task<int> AddRangeAsync<TEntity>(
        IEnumerable<TEntity> entities,
        Action<SqlGenerator<TEntity>>? configure = null,
        CancellationToken cancellationToken = default) where TEntity : class
    {
        var generator = GetSqlGenerator(configure);
        if (generator.Provider == SqlProvider.MSSQL)
        {
            var count = 0;
            var totalInstances = entities.Count();

            var properties =
                (generator.IsIdentity
                    ? generator.SqlProperties.Where(p =>
                        !p.PropertyName.Equals(generator.IdentitySqlProperty!.PropertyName,
                            StringComparison.OrdinalIgnoreCase))
                    : generator.SqlProperties).ToList();

            var exceededTimes = (int)Math.Ceiling(totalInstances * properties.Count / 2099d);
            if (exceededTimes > 1)
            {
                var maxAllowedInstancesPerBatch = totalInstances / exceededTimes;
                if (maxAllowedInstancesPerBatch > 1000) maxAllowedInstancesPerBatch = 1000;

                var maxIterationCount = (int)Math.Ceiling((double)totalInstances / (double)maxAllowedInstancesPerBatch);

                for (var i = 0; i <= maxIterationCount; i++)
                {
                    var skips = i * maxAllowedInstancesPerBatch;

                    if (skips >= totalInstances)
                        break;

                    var items = entities.Skip(skips).Take(maxAllowedInstancesPerBatch);
                    var msSqlQueryResult = generator.GetBulkInsert(items);

                    count += await ExecuteAsync(msSqlQueryResult.GetSql(), msSqlQueryResult.Param, cancellationToken)
                        .ConfigureAwait(false);
                }

                return count;
            }
        }

        var queryResult = generator.GetBulkInsert(entities);

        return await ExecuteAsync(queryResult.GetSql(), queryResult.Param, cancellationToken).ConfigureAwait(false);
    }

    /// <inheritdoc/>
    public async Task<TEntity> AddAsync<TEntity>(
        TEntity entity,
        Action<SqlGenerator<TEntity>>? configure = null,
        CancellationToken cancellationToken = default) where TEntity : class
    {
        var generator = GetSqlGenerator(configure);

        var sqlQuery = generator.GetInsert(entity);
        if (generator.IsIdentity)
        {
            if (generator.Provider == SqlProvider.Oracle)
            {
                await this.ExecuteAsync(sqlQuery.GetSql(), sqlQuery.Param, cancellationToken).ConfigureAwait(false);

                var newId = ((DynamicParameters)sqlQuery.Param!).Get<int>(":newId");
                var success = SetValue(generator, newId, entity);

                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"generate id is {newId},{success}");

                return entity;
            }
            else
            {
                var newId = await QueryAsync(sqlQuery.GetSql(), sqlQuery.Param, cancellationToken)
                    .ConfigureAwait(false);
                var success = SetValue(generator, newId, entity);

                if (logger.IsEnabled(LogLevel.Trace))
                    logger.LogTrace($"generate id is {newId},{success}");

                return entity;
            }
        }


        var effectRow = await this.ExecuteAsync(sqlQuery.GetSql(), entity, cancellationToken).ConfigureAwait(false);

        if (logger.IsEnabled(LogLevel.Trace))
            logger.LogTrace($"add effect row:{effectRow}");

        return entity;
    }

    /// <summary>
    ///
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    private bool SetValue<TEntity>(SqlGenerator<TEntity> generator, long newId, TEntity instance) where TEntity : class
    {
        var added = newId > 0;
        if (added && generator.IsIdentity)
        {
            var newParsedId = Convert.ChangeType(newId, generator.IdentitySqlProperty!.PropertyInfo.PropertyType);
            generator.IdentitySqlProperty.PropertyInfo.SetValue(instance, newParsedId);
        }

        return added;
    }
}